#include "rpcprovider.h"
#include "mprpcapplication.h"
#include "rpcheader.pb.h"
#include <cstdio>
#include "logger.h"
#include "zookeeperutil.h"
// 这里是框架提供给外部使用的，可以发布rpc方法的函数接口，不能接收具体业务，而这些业务都继承自google::protobuf::Service
void RpcProvider::NotifyService(google::protobuf::Service *service) // 这里虽然接受的是UserService()，但是我们这是框架所以不能接收具体业务，使用UserService()的基类接收
{
    ServiceInfo service_info; // 实例化一个服务对象对象类型对象
    // 获取了服务对象的描述信息
    const google::protobuf::ServiceDescriptor *pserviceDesc = service->GetDescriptor(); // 现在描述的就是我们注册的UserService对象的信息，包括它的方法啊啥的
    // 获取服务的名字
    std::string service_name = pserviceDesc->name();
    // 获取服务对象方法的数量
    int methodCnt = pserviceDesc->method_count();

    // 测试
    // std::cout << "service_name: " << service_name << std::endl;
    LOG_INFO("service_name:%s", service_name.c_str());

    for (int i = 0; i < methodCnt; ++i)
    {
        // 获取了服务对象指定下标的服务方法的描述，这是抽象描述，因为这是作为框架使用的，不可能直接把service变成Userservice
        // 把MethodDescriptor变成Login （在protobuf上就是用service和MethodDescriptor分别表示服务对象和其方法）
        const google::protobuf::MethodDescriptor *pmethodDesc = pserviceDesc->method(i);
        std::string method_name = pmethodDesc->name();

        // 接下来就可以给服务对象类型对象中添加东西了
        service_info.m_methodMap.insert({method_name, pmethodDesc});

        // 测试
        // std::cout << "method_name: " << method_name << std::endl;
        LOG_INFO("method_name:%s", method_name.c_str());
    }
    service_info.m_service = service;
    m_serviceMap.insert({service_name, service_info});
}
// 启动rpc服务节点，开始提供rpc远程网络服务调用
void RpcProvider::Run()
{
    std::string ip = MprpcApplication::GetInstance().GetConfig().Load("rpcserverip");
    uint16_t port = atoi(MprpcApplication::GetInstance().GetConfig().Load("rpcserverport").c_str());
    muduo::net::InetAddress address(ip, port); // 将参数传入

    // 接下来创建tcpserver对象
    muduo::net::TcpServer server(&m_eventLoop, address, "RpcProvider");
    // 绑定连接回调和消息读写回调方法，muduo库的好处就是让网络模块和业务模块分离开来，
    // 我们只需要关注业务（有没有新用户连接和已连接用户的读写事件），网络的部分直接交给muduo库就可以
    server.setConnectionCallback(std::bind(&RpcProvider::OnConnection, this, std::placeholders::_1)); // 这里就可以调用OnConnection了，但是它是需要绑定当前的RpcProvider对象才能调用，就是需要实例化出一个RpcProvider对象才能调，所以
                                                                                                      // 这里我们使用绑定器,即将RpcProvider::OnConnection跟当前对象(this)绑定一下
                                                                                                      // OnConnection还有一个参数，所以我们需要预留一个参数的位置

    server.setMessageCallback(std::bind(&RpcProvider::OnMessage, this, std::placeholders::_1,
                                        std::placeholders::_2, std::placeholders::_3));
    // 设置muduo库的线程数量
    server.setThreadNum(4); // 一个线程是I/O线程，另外三个是工作线程，epoll+多线程（基于reactor模型的一个服务器）

    std::cout<<"测试"<<std::endl;
    // 把当前rpc节点上要发布的服务全部注册到zk上面，让rpc client可以从zk上发现服务
    zkClient zkCli;
    zkCli.Start();
    for (auto &sp : m_serviceMap)
    {
        std::string service_path = "/" + sp.first;
        zkCli.Create(service_path.c_str(), nullptr, 0);
        for (auto &mp : sp.second.m_methodMap)
        {
            std::string method_path = service_path + "/" + mp.first;
            char method_path_data[128] = {0};
            sprintf(method_path_data, "%s:%d", ip.c_str(), port);
            // 方法都是临时性节点
            zkCli.Create(method_path.c_str(), method_path_data, strlen(method_path_data), ZOO_EPHEMERAL);
        }
    }
    // std::cout << "RpcProvider start service at ip:" << ip << " port:" << port << std::endl;
    LOG_INFO("RpcProvider start service at ip:%s,port:%d", ip.c_str(), port);
    // 启动网络服务
    server.start();
    m_eventLoop.loop();
}

// 新的socket连接回调
void RpcProvider::OnConnection(const muduo::net::TcpConnectionPtr &conn)
{
    if (!conn->connected())
    {
        // 表示和rpc client的连接断开了
        conn->shutdown();
    }
}
void RpcProvider::OnMessage(const muduo::net::TcpConnectionPtr &conn,
                            muduo::net::Buffer *buffer,
                            muduo::Timestamp)
{
    // 网络上接收的远程rpc调用的请求的字符流 即请求的方法和其参数Login和args
    std::string recv_buf = buffer->retrieveAllAsString(); //***其内容就是网络接收到的原始字节流（二进制数据）本身,然后将二进制数据本身以字符串的形式返回，而不是对这些字节进行任何字符编码后形成的“有意义的字符串”。
    // 我们不能直接将所读到的內容转成字符串的格式来读取前四个字节来确定数据头的大小，所以我们需要string中的insert和copy这个接口来实现
    uint32_t header_size = 0;
    recv_buf.copy((char *)&header_size, 4, 0); // 从recv_buf的第0个字符开始，复制4字节的数据到header_size中

    // 根据header_size读取数据头的原始字符流,接着反序列化数据，得到rpc请求的详细信息
    std::string rpc_header_str = recv_buf.substr(4, header_size); // 这段就包含了 service_name method_name args_size
    mprpc::RpcHeader rpcHeader;
    std::string service_name;
    std::string method_name;
    uint32_t args_size;
    if (rpcHeader.ParseFromString(rpc_header_str))
    {
        // 数据反序列化成功

        service_name = rpcHeader.service_name();
        method_name = rpcHeader.method_name();
        args_size = rpcHeader.args_size();
    }
    else
    {
        // 数据反序列化失败
        // std::cout << "rpc_header_str" << rpc_header_str << "parse error!" << std::endl;
        LOG_ERR("rpc_header_str: %s parse error! =>%s:%s:%d", rpc_header_str.c_str(), __FILE__, __FUNCTION__, __LINE__);
        return;
    }

    // 获取rpc方法参数的字符流数据
    std::string args_str = recv_buf.substr(4 + header_size, args_size);

    // 打印调试信息
    std::cout << "==================================================" << std::endl;
    std::cout << "recv_buf::" << recv_buf << std::endl;
    std::cout << "header_size::" << header_size << std::endl;
    std::cout << "rpc_header_str::" << rpc_header_str << std::endl;
    std::cout << "service_name::" << service_name << std::endl;
    std::cout << "method_name::" << method_name << std::endl;
    std::cout << "args_size::" << args_size << std::endl;
    std::cout << "args_str::" << args_str << std::endl;

    for (unsigned char c : args_str)
    {
        printf("%02X ", c);
    }
    printf("\n");

    std::cout << "==================================================" << std::endl;

    // 接下来获取service对象和method对象
    // ServiceInfo sinfo=m_serviceMap,这里不能使用中括号不然找不到的话，对于容器来说会有副作用(插入新的)
    auto it = m_serviceMap.find(service_name);
    if (it == m_serviceMap.end())
    {
        // 判断请求的对象不在我本地
        // std::cout << service_name << "is not exist" << std::endl;
        LOG_ERR("%s is not exist! =>%s:%s:%d", service_name.c_str(), __FILE__, __FUNCTION__, __LINE__);
        return;
    }
    // 判断请求的方法在不在我本地
    auto mit = it->second.m_methodMap.find(method_name);
    if (mit == it->second.m_methodMap.end())
    {
        // 表示方法不在
        // std::cout << service_name << ":" << method_name << "is not exist" << std::endl;
        LOG_ERR("%s : %s is not exist! =>%s:%s:%d", service_name.c_str(), method_name.c_str(), __FILE__, __FUNCTION__, __LINE__);
        return;
    }

    // 此时就知道service了，然后将要调用的这个service对象传给基类指针准备调用(例如要调用userservice，后面就要调用我们重写的Login了)
    google::protobuf::Service *service = it->second.m_service;      // 对应的就是new出来的Userservice对象，整个模块中不止这一个对象还有比如获取列表好友的GetFriendList对象
    const google::protobuf::MethodDescriptor *method = mit->second; // 对应Login方法

    // 生成rpc方法调用的请求request和response参数
    // 相当于从抽象层面上获取了我们想请求对象的请求方法的一个请求类型和响应类型
    google::protobuf::Message *request = service->GetRequestPrototype(method).New();
    if (!request->ParseFromString(args_str))//拿到远端传过来的参数并且反序列化
    {
        // std::cout << "request parse error, content:" << args_str << std::endl;
        LOG_ERR("request parse error, content:%s =>%s:%s:%d", args_str.c_str(), __FILE__, __FUNCTION__, __LINE__);
        return;
    }
    google::protobuf::Message *response = service->GetResponsePrototype(method).New();

    // 给下面的method方法的调用，绑定一个Closure的回调函数，protobuf中提供了我们需要的绑定函数newcallback（生成一个新的回调）
    // 即产生了一个对象，它是通过newback回调的
    google::protobuf::Closure *done = google::protobuf::NewCallback<RpcProvider,
                                                                    const muduo::net::TcpConnectionPtr &,
                                                                    google::protobuf::Message *>(this, &RpcProvider::SendRpcResponse, conn, response);

    // 在框架上根据远端的rpc请求，调用当前rpc节点上发布的方法
    // 下面的就相当于是new UserService().Login(controller,request,response,done)，但是这是框架调的不能用具体的对象
    //也就是最后框架调用的Login方法了
    service->CallMethod(method, nullptr, request, response, done);
    //根据参数就是调什么方法，nullptr所在的参数暂时不用关心，传入request、response,done是一个自己定义的回调函数
    //我们所希望的回调函数就是最后将响应发送回去
}
// Closure的回调操作，用于序列化rpc的响应和网络发送,所以需要Connection参数(有网络)和Message参数(发送的消息)
void RpcProvider::SendRpcResponse(const muduo::net::TcpConnectionPtr &conn, google::protobuf::Message *response)
{
    // 这里先对回应的消息进行序列化，再通过网络进行发送给rpc的调用方
    std::string response_str;
    if (response->SerializeToString(&response_str))
    {
        conn->send(response_str);
    }
    else
    {
        // std::cout << "serialize response_str error!" << std::endl;
        LOG_ERR("serialize response_str error! =>%s:%s:%d", __FILE__, __FUNCTION__, __LINE__);
    }
    conn->shutdown(); // 模拟http的短链接服务，由rpcprovider主动断开连接
}
